/*
 * Copyright 2007-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.paoding.rose.web.ControllerErrorHandler;
import net.paoding.rose.web.ControllerInterceptor;
import net.paoding.rose.web.annotation.ReqMethod;
import net.paoding.rose.web.impl.mapping.Mapping;
import net.paoding.rose.web.impl.mapping.MappingFactory;
import net.paoding.rose.web.impl.module.Module;
import net.paoding.rose.web.impl.thread.InvocationBean;
import net.paoding.rose.web.impl.thread.MatchResult;
import net.paoding.rose.web.impl.thread.ModuleEngine;
import net.paoding.rose.web.impl.thread.RequestPath;
import net.paoding.rose.web.impl.thread.WebInvocation;
import net.paoding.rose.web.instruction.InstructionExecutor;
import net.paoding.rose.web.instruction.InstructionExecutorImpl;
import net.paoding.rose.web.var.FlashImpl;
import net.paoding.rose.web.var.PrivateVar;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.util.NestedServletException;

/**
 * {@link RoseEngine}从{@link RoseFilter}接收web请求，并按照Rose规则进行处理。
 * <p>
 * 
 * {@link RoseEngine}会判断该web请求是否是本{@link RoseEngine}应该处理的，如果是进行后续的委派，如果不是则只直接返回。
 * <p>
 * 
 * @author qieqie.wang@paoding.net
 * 
 */
public class RoseEngine {

	// ------------------------------------------------------------
	
	private Log logger = LogFactory.getLog(getClass());

	protected final Mapping<ModuleEngine>[] moduleMappings;
	
	protected WebInvocation userProfile;
	
	protected InstructionExecutor instructionExecutor;
	
	// ------------------------------------------------------------

	/**
	 * 
	 * @param modules
	 */
	public RoseEngine(List<Module> modules) {
		this.moduleMappings = createMappings(modules);
		this.instructionExecutor = new InstructionExecutorImpl();
	}
	
	// ------------------------------------------------------------

	/**
	 * 
	 * @param instructionExecutor
	 */
	public void setInstructionExecutor(InstructionExecutor instructionExecutor) {
		this.instructionExecutor = instructionExecutor;
	}

	/**
	 * 
	 * @param userProfile
	 */
	public void setUserProfile(WebInvocation userProfile) {
		this.userProfile = userProfile;
	}
	
	/**
	 * 
	 * @return
	 */
	public Mapping<ModuleEngine>[] getModuleMappings() {
		return Arrays.copyOf(moduleMappings, moduleMappings.length);
	}

	@SuppressWarnings("unchecked")
	private Mapping<ModuleEngine>[] createMappings(List<Module> modules) {
		Mapping<ModuleEngine>[] mappings = new Mapping[modules.size()];
		for (int i = 0; i < mappings.length; i++) {
			Module module = modules.get(i);
			ModuleEngine moduleEngine = new ModuleEngine(module);
			mappings[i] = MappingFactory.createMapping( //
					module.getPath(), MappingFactory.MODE_STARTS_WITH, //
					new ReqMethod[] { ReqMethod.ALL }, moduleEngine); // 
		}
		Arrays.sort(mappings);
		return mappings;
	}
	
	/**
	 * 
	 * @param request
	 * @param response
	 * @return
	 * @throws ServletException
	 */
	public Object invoke(final HttpServletRequest request, final HttpServletResponse response, //
			final RequestPath requestPath) throws NestedServletException {
		
		// 从moduleMappings中按顺序判断本请求是否应该由该模块处理
		Mapping<ModuleEngine> moduleMapping = searchModule(requestPath);
		
		if (moduleMapping == null) {
			// 没有从moduleMappings找到这个请求地址的模块，向上返回CONTINUE表示该请求不应由Rose处理
			return RoseConstants.CONTINUE;
		}
		// OK，这个请求由这个module来尝试处理!
		ModuleEngine moduleEngine = moduleMapping.getMappingTarget();
		InvocationBean invocation = moduleEngine.findInvocation(request, response, requestPath);
		if (invocation == null) {
			return RoseConstants.CONTINUE;
		}
		
		try {
			if (requestPath.isIncludeRequest()) {
				saveAttributesBeforeInclude(request, requestPath);
			}
			
			// 将mactchResult的参数放入model
			for (String matchResultParam : invocation.getControllerMatchResult().getParameterNames()) {
				invocation.getModel().add(matchResultParam, invocation.getControllerMatchResult().getParameter(matchResultParam));
			}
			for (String matchResultParam : invocation.getActionMatchResult().getParameterNames()) {
				invocation.getModel().add(matchResultParam, invocation.getActionMatchResult().getParameter(matchResultParam));
			}
			
			// instruction是控制器action方法的返回结果或其对应的Instruction对象(也可能是拦截器、错误处理器返回的)
			Object instruction;
			try {
				invocation.setMultiPartRequest(checkMultipart(invocation));

				// 执行对actionEngine的调用(拦截器、控制器、错误处理器等一整个套体系)
				instruction = invoke(invocation);
			} finally {
				if (invocation.isMultiPartRequest()) {
					cleanupMultipart(invocation);
				}
			}

			// 应用用户有关的设置：比如地区设置之类的等等
			if (userProfile != null) {
				userProfile.invoke(invocation);
			}
			
			// 写flash消息到Cookie (被include的请求不会有功能)
			if (!requestPath.isIncludeRequest()) {
				FlashImpl flash = (FlashImpl) invocation.getFlash(false);
				if (flash != null) {
					flash.writeNewMessages();
				}
			}
			
			// 渲染页面
			instruction = instructionExecutor.render(invocation, instruction);
			
			// 渲染后的操作：拦截器的afterCompletion以及include属性快照的恢复等
			afterCompletion(invocation,  (Exception) null);
			
			//
			return instruction;
		}
		catch (Throwable exception) {
			// 异常后的操作(可能还没渲染)：拦截器的afterCompletion以及include属性快照的恢复等
			afterCompletion(invocation, exception);
			//
			NestedServletException servletException = new NestedServletException(
					"error happended for request:" + requestPath.getMethod()
					+ " "+ requestPath.getUri(), exception);
			//
			logger.error(servletException);
			//
			throw servletException;
		}
	}
	
	public void destroy() {
	}

	/**
	 * 
	 * @param requestPath
	 * @return
	 */
	private Mapping<ModuleEngine> searchModule(final RequestPath requestPath) {
		Mapping<ModuleEngine> moduleMapping = null;
		MatchResult moduleMatchResult = null;
		for (int i = 0; i < moduleMappings.length; i++) {
			moduleMatchResult = moduleMappings[i].match(
					requestPath.getPathInfo(), requestPath.getMethod());
			if (moduleMatchResult != null) {
				// !!found!!
				moduleMapping = moduleMappings[i];
				requestPath.setModulePath(moduleMatchResult.getMatchedString());
				break;
			}
		}
		return moduleMapping;
	}

	private Object invoke(InvocationBean invocation)
			throws Exception, Error {
		Object instruction = null;
		try {
			instruction = invocation.invoke();
		} catch (Throwable invocationException) {
			// 抛出异常了(可能是拦截器或控制器抛出的)，此时让该控制器所在模块的ControllerErrorHanlder处理
			
			Throwable cause = invocationException;
			// 因为使用的是类反射技术，所以需要先把实际异常从InvocationTargetException取出来
			while (cause instanceof InvocationTargetException) {
				cause = ((InvocationTargetException) cause).getTargetException();
			}
			// 
			Module module = invocation.getModule();
			ControllerErrorHandler errorHandler = module.getErrorHandler();
			if (errorHandler != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("exception happended, ControllerErrorHandler in module '" //
								+ module.getPath() + "' will handler the exception: " //
								+ cause.getClass().getName() + ":" + cause.getMessage()); //
				}
				instruction = errorHandler.onError(cause);
			}
			
			// onError方法返回null，表示需要重新throw出去
			// rethrow出去的不是cause而是invocationException，目的要把整个异常抛出来，以让知道整个异常的来由
			if (errorHandler == null || instruction == null) {
				if (invocationException instanceof Exception) {
					throw (Exception) invocationException;
				} else {
					throw (Error) invocationException;
				}
			}
		}
		return instruction;
	}
	

	private void saveAttributesBeforeInclude(HttpServletRequest request,
			final RequestPath requestPath) {
		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		logger.debug("Taking snapshot of request"
				+ " attributes before include");
		Map<String, Object> attributesSnapshot = new HashMap<String, Object>();
		Enumeration<?> attrNames = request.getAttributeNames();
		while (attrNames.hasMoreElements()) {
			String attrName = (String) attrNames.nextElement();
			attributesSnapshot
					.put(attrName, request.getAttribute(attrName));
		}
		PrivateVar.getInvocation().setAttributesSnapshot(
				attributesSnapshot);
	}

	private void afterCompletion(InvocationBean invocation, Throwable e) {
		// 触发拦截器的afterCompletion接口
		triggerAfterCompletion(invocation, e);
		
		// 恢复include请求前的各种请求属性(包括Model对象)
		if (invocation.getRequestPath().isIncludeRequest()) {
			restoreAttributesAfterInclude(invocation.getRequest());
		}
	}
	
	private void triggerAfterCompletion(InvocationBean invocation, Throwable exceptionByModule) {
		// Apply afterCompletion methods of registered interceptors.
		ControllerInterceptor[] interceptors = invocation.getActionEngine().getRegisteredInterceptors();
		for (int i = interceptors.length - 1; i >= 0; i--) {
			ControllerInterceptor interceptor = interceptors[i];
			try {
				interceptor.afterCompletion(invocation, exceptionByModule);
			} catch (Throwable thisException) {
				logger.error("ControllerInterceptor.afterCompletion threw exception", thisException);
			}
		}
	}

	/**
	 * Restore the request attributes after an include.
	 * 
	 * @param request
	 *            current HTTP request
	 * @param attributesSnapshot
	 *            the snapshot of the request attributes before the include
	 */
	private void restoreAttributesAfterInclude(HttpServletRequest request) {
		logger.debug("Restoring snapshot of request attributes after include");

		Map<String, Object> attributesSnapshot = PrivateVar.getInvocation()
				.getAttributesSnapshot();
		PrivateVar.getInvocation().setAttributesSnapshot(null);
		
		// Need to copy into separate Collection here, to avoid side effects
		// on the Enumeration when removing attributes.
		Set<String> attrsToCheck = new HashSet<String>();
		Enumeration<?> attrNames = request.getAttributeNames();
		while (attrNames.hasMoreElements()) {
			String attrName = (String) attrNames.nextElement();
			attrsToCheck.add(attrName);
		}

		// Iterate over the attributes to check, restoring the original value
		// or removing the attribute, respectively, if appropriate.
		for (Iterator<String> it = attrsToCheck.iterator(); it.hasNext();) {
			String attrName = it.next();
			Object attrValue = attributesSnapshot.get(attrName);
			if (attrValue != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Restoring original value of attribute [" + attrName
							+ "] after include");
				}
				request.setAttribute(attrName, attrValue);
			} else {
				if (logger.isDebugEnabled()) {
					logger.debug("Removing attribute [" + attrName + "] after include");
				}
				request.removeAttribute(attrName);
			}
		}
	}

	/**
	 * Convert the request into a multipart request, and make multipart resolver
	 * available. If no multipart resolver is set, simply use the existing
	 * request.
	 * 
	 * @param request
	 *            current HTTP request
	 * @return the processed request (multipart wrapper if necessary)
	 * @see MultipartResolver#resolveMultipart
	 */
	protected boolean checkMultipart(InvocationBean invocation)
			throws MultipartException {
		Module module = invocation.getModule();
		if (module.getMultipartResolver() != null
				&& module.getMultipartResolver().isMultipart(invocation.getRequest())) {
			if (invocation.getRequest() instanceof MultipartHttpServletRequest) {
				logger.debug("Request is already a "
						+ "MultipartHttpServletRequest");
			} else {
				invocation.setRequest(invocation.getModule().getMultipartResolver()
						.resolveMultipart(invocation.getRequest()));
			}
			return true;
		}
		return false;
	}

	/**
	 * Clean up any resources used by the given multipart request (if any).
	 * 
	 * @param request
	 *            current HTTP request
	 * @see MultipartResolver#cleanupMultipart
	 */
	protected void cleanupMultipart(InvocationBean invocation) {
		if (invocation.getRequest() instanceof MultipartHttpServletRequest) {
			invocation.getModule().getMultipartResolver()
					.cleanupMultipart((MultipartHttpServletRequest) invocation.getRequest());
		}
	}
	

}
